package sindconsole.misc.webapp;

import common.annotation.Traced;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.subject.Subject;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import sindconsole.auth.bean.UserPrincipal;
import sindconsole.misc.bean.OperationTrace;
import sindconsole.misc.service.OperationServiceImpl;

import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.util.Date;
import java.util.LinkedHashMap;
import java.util.Map;

@Slf4j
@Aspect
@Component
@Order(1000)
public class TracedAspect {

    @Autowired
    OperationServiceImpl operationService;

    @Pointcut("@annotation(common.annotation.Traced)")
    public void trace() {}

    @AfterReturning(value = "trace()", returning="returning")
    public void doTrace(JoinPoint joinPoint, Object returning) {
        logTrace(joinPoint, returning, true);
    }

    @AfterThrowing(value = "trace()", throwing="throwing")
    public void doTrace(JoinPoint joinPoint, Throwable throwing) {
        logTrace(joinPoint, throwing, false);
    }

    void logTrace(JoinPoint joinPoint, Object result, boolean resultOk) {
        OperationTrace trace = new OperationTrace();
        MethodSignature signature = (MethodSignature)joinPoint.getSignature();
        Method method = signature.getMethod();
        Traced traced = method.getAnnotation(Traced.class);
        trace.setOperation(traced.value());
        if(!StringUtils.hasText(trace.getOperation())) {
            ApiOperation apiOperation = method.getAnnotation(ApiOperation.class);
            if(apiOperation != null) trace.setOperation(apiOperation.value());
        }
        if(!StringUtils.hasText(trace.getOperation())) {
            trace.setOperation(method.getName());
        }
        String methodString = joinPoint.getTarget().getClass().getName() + "." + method.getName();
        trace.setMethod(prune(methodString, 255));
        Map<String, Object> argMap = new LinkedHashMap<>();
        for(int i = 0; i < signature.getParameterNames().length - 1; i++) {
            argMap.put(signature.getParameterNames()[i], joinPoint.getArgs()[i]);
        }
        trace.setResultOk(resultOk);
        if(!argMap.isEmpty()) trace.setArgs(prune(argMap.toString(), 1024));
        if(result != null) trace.setResult(prune(result.toString(), 1024));
        Subject subject = SecurityUtils.getSubject();
        if(subject != null) {
            UserPrincipal principle = (UserPrincipal)subject.getPrincipal();
            if(principle != null) trace.setUserId(principle.getUser().getId());
        }
        trace.setLoggedTime(new Date());
        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
        trace.setLoggedAddr(request.getRemoteAddr());

        operationService.createTrace(trace);
    }

    static String prune(String raw, int length) {
        return raw.length() < length ? raw : raw.substring(0, length);
    }

}
